﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Go;

namespace fhs
{
    abstract class McProtocel
    {
        static private byte[] _readAck = { 0xD0, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00 };
        static private byte[] _writeAck = { 0xD0, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00 };
        private const int writeHeadSize = 21;
        private const int ackSize = 11;

        public class byte_buff
        {
            public byte[] buf = new byte[1600];
            public int ackCur = 0;
            public int ackCnt = 0;

            public void reset()
            {
                ackCur = 0;
                ackCnt = 0;
            }
        }

        static public byte[] read_single_word_d(int addr)
        {
            return new byte[] { 0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x04, 0x00, 0x00
                            , (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16)
                            , 0xA8, 0x01, 0x00 };
        }

        static public byte[] read_single_dword_d(int addr)
        {
            return new byte[] { 0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x04, 0x00, 0x00
                            , (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16)
                            , 0xA8, 0x02, 0x00};
        }

        static public byte[] write_single_word_d(int addr, ushort val)
        {
            return new byte[] { 0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00
                            , 0x0E, 0x00
                            , 0x01, 0x00, 0x01, 0x14, 0x00, 0x00
                            , (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16)
                            , 0xA8, 0x01, 0x00
                            , (byte)val, (byte)(val >> 8) };
        }

        static public byte[] write_single_dword_d(int addr, uint value)
        {
            return new byte[] { 0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00
                            , 0x10, 0x00
                            , 0x01, 0x00, 0x01, 0x14, 0x00, 0x00
                            , (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16)
                            , 0xA8, 0x02, 0x00
                            , (byte)(value), (byte)(value >> 8), (byte)(value >> 16), (byte)(value >> 24) };
        }

        static public byte[] read_multi_word_d(int addr, int cnt)
        {
            return new byte[] { 0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x04, 0x00, 0x00
                            , (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16)
                            , 0xA8, (byte)cnt, (byte)(cnt >> 8) };
        }

        static public bool check_read_multi_word_d(byte_buff stream, int bufLen, ushort[] value, out bool completed)
        {
            int bufCur = 0;
            for (; bufCur < bufLen && stream.ackCur < _readAck.Length; ++stream.ackCur, ++bufCur)
            {
                if (stream.buf[bufCur] != _readAck[stream.ackCur])
                {
                    completed = true;
                    return false;
                }
            }
            for (; bufCur < bufLen && stream.ackCur < ackSize; ++stream.ackCur, ++bufCur)
            {
                stream.ackCnt |= stream.buf[bufCur] << (stream.ackCur - _readAck.Length);
            }
            if (stream.ackCur >= ackSize)
            {
                int cnt = stream.ackCnt - 2;
                if (cnt < 0 || cnt > value.Length * 2)
                {
                    completed = true;
                    return false;
                }
                for (; bufCur < bufLen && stream.ackCur < ackSize + cnt; ++stream.ackCur, ++bufCur)
                {
                    int ct = stream.ackCur - ackSize;
                    if (0 == ct % 2)
                    {
                        value[ct / 2] = stream.buf[bufCur];
                    }
                    else
                    {
                        value[ct / 2] |= (ushort)((uint)stream.buf[bufCur] << 8);
                    }
                }
                completed = stream.ackCur == ackSize + cnt;
                return true;
            }
            completed = false;
            return true;
        }

        static public byte[] read_multi_dword_d(int addr, int cnt)
        {
            return new byte[] { 0x50, 0x00, 0x00, 0xFF, 0xFF, 0x03, 0x00, 0x0C, 0x00, 0x01, 0x00, 0x01, 0x04, 0x00, 0x00
                            , (byte)addr, (byte)(addr >> 8), (byte)(addr >> 16)
                            , 0xA8, (byte)(cnt * 2), (byte)((cnt * 2) >> 8) };
        }

        static public bool check_read_multi_dword_d(byte_buff stream, int bufLen, uint[] value, out bool completed)
        {
            int bufCur = 0;
            for (; bufCur < bufLen && stream.ackCur < _readAck.Length; ++stream.ackCur, ++bufCur)
            {
                if (stream.buf[bufCur] != _readAck[stream.ackCur])
                {
                    completed = true;
                    return false;
                }
            }
            for (; bufCur < bufLen && stream.ackCur < ackSize; ++stream.ackCur, ++bufCur)
            {
                stream.ackCnt |= stream.buf[bufCur] << (stream.ackCur - _readAck.Length);
            }
            if (stream.ackCur >= ackSize)
            {
                int cnt = stream.ackCnt - 2;
                if (cnt < 0 || cnt > value.Length * 4)
                {
                    completed = true;
                    return false;
                }
                for (; bufCur < bufLen && stream.ackCur < ackSize + cnt; ++stream.ackCur, ++bufCur)
                {
                    int ct = stream.ackCur - ackSize;
                    int shift = ct % 4;
                    if (0 == shift)
                    {
                        value[ct / 4] = stream.buf[bufCur];
                    }
                    else
                    {
                        value[ct / 4] |= (uint)stream.buf[bufCur] << (shift * 8);
                    }
                }
                completed = stream.ackCur == ackSize + cnt;
                return true;
            }
            completed = false;
            return true;
        }

        static public byte[] write_multi_word_d(int addr, ushort[] value)
        {
            int j = 0;
            int length = value.Length;
            byte[] dataPack = new byte[writeHeadSize + length * 2];
            dataPack[j++] = 0x50; dataPack[j++] = 0x00; dataPack[j++] = 0x00; dataPack[j++] = 0xFF; dataPack[j++] = 0xFF; dataPack[j++] = 0x03; dataPack[j++] = 0x00;
            dataPack[j++] = (byte)(0x0C + length * 2); dataPack[j++] = (byte)((0x0C + length * 2) >> 8);
            dataPack[j++] = 0x01; dataPack[j++] = 0x00; dataPack[j++] = 0x01; dataPack[j++] = 0x14; dataPack[j++] = 0x00; dataPack[j++] = 0x00;
            dataPack[j++] = (byte)addr; dataPack[j++] = (byte)(addr >> 8); dataPack[j++] = (byte)(addr >> 16);
            dataPack[j++] = 0xA8; dataPack[j++] = (byte)length; dataPack[j++] = (byte)(length >> 8);
            for (int i = 0; i < length; ++i)
            {
                dataPack[writeHeadSize + i * 2] = (byte)value[i];
                dataPack[writeHeadSize + i * 2 + 1] = (byte)(value[i] >> 8);
            }
            return dataPack;
        }

        static public bool check_write_multi_word_d(byte_buff stream, int bufLen, out bool completed)
        {
            int bufCur = 0;
            for (; bufCur < bufLen && stream.ackCur < _writeAck.Length; ++stream.ackCur, ++bufCur)
            {
                if (stream.buf[bufCur] != _writeAck[stream.ackCur])
                {
                    completed = true;
                    return false;
                }
            }
            completed = stream.ackCur == ackSize;
            return true;
        }

        static public byte[] write_multi_dword_d(int addr, uint[] value)
        {
            int j = 0;
            int length = value.Length;
            byte[] dataPack = new byte[writeHeadSize + length * 4];
            dataPack[j++] = 0x50; dataPack[j++] = 0x00; dataPack[j++] = 0x00; dataPack[j++] = 0xFF; dataPack[j++] = 0xFF; dataPack[j++] = 0x03; dataPack[j++] = 0x00;
            dataPack[j++] = (byte)(0x0C + length * 4); dataPack[j++] = (byte)((0x0C + length * 4) >> 8);
            dataPack[j++] = 0x01; dataPack[j++] = 0x00; dataPack[j++] = 0x01; dataPack[j++] = 0x14; dataPack[j++] = 0x00; dataPack[j++] = 0x00;
            dataPack[j++] = (byte)addr; dataPack[j++] = (byte)(addr >> 8); dataPack[j++] = (byte)(addr >> 16);
            dataPack[j++] = 0xA8; dataPack[j++] = (byte)(length * 2); dataPack[j++] = (byte)((length * 2) >> 8);
            for (int i = 0; i < length; ++i)
            {
                dataPack[writeHeadSize + i * 4] = (byte)value[i];
                dataPack[writeHeadSize + i * 4 + 1] = (byte)(value[i] >> 8);
                dataPack[writeHeadSize + i * 4 + 2] = (byte)(value[i] >> 16);
                dataPack[writeHeadSize + i * 4 + 3] = (byte)(value[i] >> 24);
            }
            return dataPack;
        }

        static public bool check_write_multi_dword_d(byte_buff stream, int bufLen, out bool completed)
        {
            return check_write_multi_word_d(stream, bufLen, out completed);
        }
    }

    public class mc_socket
    {
        const int mctimeout = 60000;

        socket_tcp _socket = new socket_tcp();
        McProtocel.byte_buff _ioBuff = new McProtocel.byte_buff();
        string _ip;
        int _port;

        public mc_socket(string ip, int port)
        {
            _ip = ip;
            _port = port;
        }

        public ValueTask<socket_result> open()
        {
            return _socket.connect(_ip, _port);
        }

        public void close()
        {
            _socket.close();
        }

        public async Task<bool> write_single_word_d(int addr, ushort val)
        {
            tuple<bool, bool> res = await generator.timed_async_call(mctimeout, delegate (Action<bool> cb)
            {
                async_write_single_word_d(addr, val, cb);
            });
            return res.value1 && res.value2;
        }

        private void async_write_single_word_d(int addr, ushort val, Action<bool> cb)
        {
            byte[] data = McProtocel.write_single_word_d(addr, val);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_write_multi_word_d(_ioBuff, rres.s, out completed))
                                {
                                    cb(false);
                                }
                                else if (completed)
                                {
                                    cb(true);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false);
                }
            });
        }

        public async Task<tuple<bool, ushort>> read_single_word_d(int addr)
        {
            tuple<bool, tuple<bool, ushort>> res = await generator.timed_async_call(mctimeout, delegate (Action<bool, ushort> cb)
            {
                async_read_single_word_d(addr, cb);
            });
            return res.value1 ? res.value2 : tuple.make(false, (ushort)0);
        }

        private void async_read_single_word_d(int addr, Action<bool, ushort> cb)
        {
            byte[] data = McProtocel.read_single_word_d(addr);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    ushort[] aval = new ushort[1];
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_read_multi_word_d(_ioBuff, rres.s, aval, out completed))
                                {
                                    cb(false, 0);
                                }
                                else if (completed)
                                {
                                    cb(true, aval[0]);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false, 0);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false, 0);
                }
            });
        }

        public async Task<bool> write_multi_word_d(int addr, ushort[] vals)
        {
            tuple<bool, bool> res = await generator.timed_async_call(mctimeout, delegate (Action<bool> cb)
            {
                async_write_multi_word_d(addr, vals, cb);
            });
            return res.value1 && res.value2;
        }

        private void async_write_multi_word_d(int addr, ushort[] vals, Action<bool> cb)
        {
            byte[] data = McProtocel.write_multi_word_d(addr, vals);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_write_multi_word_d(_ioBuff, rres.s, out completed))
                                {
                                    cb(false);
                                }
                                else if (completed)
                                {
                                    cb(true);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false);
                }
            });
        }

        public async Task<tuple<bool, ushort[]>> read_multi_word_d(int addr, int cnt)
        {
            tuple<bool, tuple<bool, ushort[]>> res = await generator.timed_async_call(mctimeout, delegate (Action<bool, ushort[]> cb)
            {
                async_read_multi_word_d(addr, cnt, cb);
            });
            return res.value1 ? res.value2 : tuple.make(false, (ushort[])null);
        }

        private void async_read_multi_word_d(int addr, int cnt, Action<bool, ushort[]> cb)
        {
            byte[] data = McProtocel.read_multi_word_d(addr, cnt);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    ushort[] buf = new ushort[cnt];
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_read_multi_word_d(_ioBuff, rres.s, buf, out completed))
                                {
                                    cb(false, null);
                                }
                                else if (completed)
                                {
                                    cb(true, buf);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false, null);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false, null);
                }
            });
        }

        public async Task<bool> read_multi_word_d(int addr, ushort[] buf)
        {
            tuple<bool, bool> res = await generator.timed_async_call(mctimeout, delegate (Action<bool> cb)
            {
                async_read_multi_word_d(addr, buf, cb);
            });
            return res.value1 ? res.value2 : false;
        }

        private void async_read_multi_word_d(int addr, ushort[] buf, Action<bool> cb)
        {
            byte[] data = McProtocel.read_multi_word_d(addr, buf.Length);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_read_multi_word_d(_ioBuff, rres.s, buf, out completed))
                                {
                                    cb(false);
                                }
                                else if (completed)
                                {
                                    cb(true);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false);
                }
            });
        }

        public async Task<bool> write_single_dword_d(int addr, uint val)
        {
            tuple<bool, bool> res = await generator.timed_async_call(mctimeout, delegate (Action<bool> cb)
            {
                async_write_single_dword_d(addr, val, cb);
            });
            return res.value1 && res.value2;
        }

        private void async_write_single_dword_d(int addr, uint val, Action<bool> cb)
        {
            byte[] data = McProtocel.write_single_dword_d(addr, val);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_write_multi_dword_d(_ioBuff, rres.s, out completed))
                                {
                                    cb(false);
                                }
                                else if (completed)
                                {
                                    cb(true);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false);
                }
            });
        }

        public async Task<tuple<bool, uint>> read_single_dword_d(int addr)
        {
            tuple<bool, tuple<bool, uint>> res = await generator.timed_async_call(mctimeout, delegate (Action<bool, uint> cb)
            {
                async_read_single_dword_d(addr, cb);
            });
            return res.value1 ? res.value2 : tuple.make(false, (uint)0);
        }

        private void async_read_single_dword_d(int addr, Action<bool, uint> cb)
        {
            byte[] data = McProtocel.read_single_dword_d(addr);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    uint[] aval = new uint[1];
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_read_multi_dword_d(_ioBuff, rres.s, aval, out completed))
                                {
                                    cb(false, 0);
                                }
                                else if (completed)
                                {
                                    cb(true, aval[0]);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false, 0);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false, 0);
                }
            });
        }

        public async Task<bool> write_multi_dword_d(int addr, uint[] vals)
        {
            tuple<bool, bool> res = await generator.timed_async_call(mctimeout, delegate (Action<bool> cb)
            {
                async_write_multi_dword_d(addr, vals, cb);
            });
            return res.value1 && res.value2;
        }

        private void async_write_multi_dword_d(int addr, uint[] vals, Action<bool> cb)
        {
            byte[] data = McProtocel.write_multi_dword_d(addr, vals);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_write_multi_dword_d(_ioBuff, rres.s, out completed))
                                {
                                    cb(false);
                                }
                                else if (completed)
                                {
                                    cb(true);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false);
                }
            });
        }

        public async Task<tuple<bool, uint[]>> read_multi_dword_d(int addr, int cnt)
        {
            tuple<bool, tuple<bool, uint[]>> res = await generator.timed_async_call(mctimeout, delegate (Action<bool, uint[]> cb)
            {
                async_read_multi_dword_d(addr, cnt, cb);
            });
            return res.value1 ? res.value2 : tuple.make(false, (uint[])null);
        }

        private void async_read_multi_dword_d(int addr, int cnt, Action<bool, uint[]> cb)
        {
            byte[] data = McProtocel.read_multi_dword_d(addr, cnt);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    uint[] buf = new uint[cnt];
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_read_multi_dword_d(_ioBuff, rres.s, buf, out completed))
                                {
                                    cb(false, null);
                                }
                                else if (completed)
                                {
                                    cb(true, buf);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false, null);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false, null);
                }
            });
        }

        public async Task<bool> read_multi_dword_d(int addr, uint[] buf)
        {
            tuple<bool, bool> res = await generator.timed_async_call(mctimeout, delegate (Action<bool> cb)
            {
                async_read_multi_dword_d(addr, buf, cb);
            });
            return res.value1 ? res.value2 : false;
        }

        private void async_read_multi_dword_d(int addr, uint[] buf, Action<bool> cb)
        {
            byte[] data = McProtocel.read_multi_dword_d(addr, buf.Length);
            _socket.async_write(new ArraySegment<byte>(data), delegate (socket_result wres)
            {
                if (wres.ok)
                {
                    _ioBuff.reset();
                    Action check = null;
                    check = delegate ()
                    {
                        _socket.async_read_same(new ArraySegment<byte>(_ioBuff.buf), delegate (socket_result rres)
                        {
                            if (rres.ok)
                            {
                                bool completed = false;
                                if (!McProtocel.check_read_multi_dword_d(_ioBuff, rres.s, buf, out completed))
                                {
                                    cb(false);
                                }
                                else if (completed)
                                {
                                    cb(true);
                                }
                                else
                                {
                                    check();
                                }
                            }
                            else
                            {
                                cb(false);
                            }
                        });
                    };
                    check();
                }
                else
                {
                    cb(false);
                }
            });
        }
    }
}
